/* run2_gpl -- Core routines for the run2 wrapper which are GPL
 * Copyright (C) 1998,2009  Charles S. Wilson
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 */
#ifdef _MSC_VER
# define _CRT_SECURE_NO_DEPRECATE 1
#endif

#if HAVE_CONFIG_H
# include <config.h>
#endif

#define WIN32

/* want windows XP or above */
#define WIN32_LEAN_AND_MEAN
#define _WIN32_WINNT 0x0501
#define NOMINMAX
#include <windows.h>

#if STDC_HEADERS
# include <stdlib.h>
# include <stdarg.h>
# include <string.h>
# include <float.h>
#endif

#include <stdio.h>
#include <winuser.h>

#if HAVE_SYS_TYPES_H
# include <sys/types.h>
#endif
#if HAVE_UNISTD_H
# include <unistd.h>
#endif

#include "lib/util.h"
#include "lib/confdata.h"
#include "run2_gpl.h"

/* Copy cygwin environment variables to the Windows environment if they're not
 * already there. */
void
setup_win_environ (void)
{
  char **envp = environ;
  char *var, *val;
  char curval[2];

  while (envp && *envp)
    {
      var = strdup (*envp++);
      val = strchr (var, '=');
      if (val != NULL)
        {
          *val++ = '\0';

          if (GetEnvironmentVariable (var, curval, 2) == 0 &&
              GetLastError () == ERROR_ENVVAR_NOT_FOUND)
            {
              SetEnvironmentVariable (var, val);
            }
        }
      free (var);
    }
}

BOOL
run2_setup_invisible_console ()
{
  HWINSTA h, horig;
  USEROBJECTFLAGS oi;
  DWORD len;
  BOOL b = FALSE;
  HMODULE lib = NULL;
  HWINSTA WINAPI (*GetProcessWindowStationFP) (void) = NULL;
  BOOL WINAPI (*GetUserObjectInformationFP) (HANDLE, int, PVOID, DWORD,
                                             PDWORD) = NULL;
  HWINSTA WINAPI (*CreateWindowStationFP) (LPCWSTR, DWORD, DWORD,
                                           LPSECURITY_ATTRIBUTES) = NULL;
  BOOL WINAPI (*SetProcessWindowStationFP) (HWINSTA) = NULL;
  BOOL WINAPI (*CloseWindowStationFP) (HWINSTA) = NULL;

  /* until we have a mechanism of determining whether a given HANDLE 
   * returned by GetStdHandles actually derives from a console, 
   * unconditionally call FreeConsole() on all OSes under all conditions.
   * See comments in configure_startupinfo(). 
   */
  FreeConsole ();

  /* First, set up function pointers */
  if (lib = LoadLibrary ("user32.dll"))
    {
      GetProcessWindowStationFP = (HWINSTA WINAPI (*)(void))
        GetProcAddress (lib, "GetProcessWindowStation");
      GetUserObjectInformationFP = (BOOL WINAPI (*)(HANDLE, int, PVOID, DWORD, PDWORD)) GetProcAddress (lib, "GetUserObjectInformationW");      /* ugly! */
      CreateWindowStationFP = (HWINSTA WINAPI (*)(LPCWSTR, DWORD, DWORD, LPSECURITY_ATTRIBUTES)) GetProcAddress (lib, "CreateWindowStationW");  /* ugly */
      SetProcessWindowStationFP = (BOOL WINAPI (*)(HWINSTA))
        GetProcAddress (lib, "SetProcessWindowStation");
      CloseWindowStationFP = (BOOL WINAPI (*)(HWINSTA))
        GetProcAddress (lib, "CloseWindowStation");

      if (GetProcessWindowStationFP &&
          GetUserObjectInformationFP &&
          CreateWindowStationFP &&
          SetProcessWindowStationFP && CloseWindowStationFP)
        {
          /* Then, do the work */
          FreeConsole ();
          h = horig = (*GetProcessWindowStationFP) ();
          if (!horig
              || !(*GetUserObjectInformationFP) (horig, UOI_FLAGS, &oi,
                                                 sizeof (oi), &len)
              || !(oi.dwFlags & WSF_VISIBLE))
            {
              b = AllocConsole ();
            }
          else
            {
              h =
                (*CreateWindowStationFP) (NULL, 0, STANDARD_RIGHTS_READ,
                                          NULL);
              if (h)
                {
                  b = (*SetProcessWindowStationFP) (h);
                }
              b = AllocConsole ();
              if (horig && h && h != horig
                  && (*SetProcessWindowStationFP) (horig))
                {
                  (*CloseWindowStationFP) (h);
                }
            }
          return b;
        }
    }
  /* otherwise, fail */
  return FALSE;
}

/* returns FALSE only on error conditions (not impl) */
BOOL
run2_configure_startupinfo (STARTUPINFO * psi, BOOL bHaveInvisConsole,
                            BOOL * bUsingPipes,
                            HANDLE * hpToChild, HANDLE * hpFromChild,
                            HANDLE * hpToParent, HANDLE * hpFromParent)
{
  CONSOLE_CURSOR_INFO cci;
  SECURITY_ATTRIBUTES handle_attrs;
  HANDLE hpStdInput[2];
  HANDLE hpStdOutput[2];

  ZeroMemory (psi, sizeof (STARTUPINFO));
  psi->cb = sizeof (STARTUPINFO);
  psi->hStdInput = GetStdHandle (STD_INPUT_HANDLE);
  psi->hStdOutput = GetStdHandle (STD_OUTPUT_HANDLE);
  psi->hStdError = GetStdHandle (STD_ERROR_HANDLE);
  psi->dwFlags = STARTF_USESHOWWINDOW | STARTF_USESTDHANDLES;
  psi->wShowWindow = SW_HIDE;

  /* foo() is some magic mechanism for determining that the HANDLEs 
   * returned by GetStdHandle() are from a console, and not redirected
   * or ptys of some sort.  If we have such a mechanism, then the 
   * unconditional FreeConsole() at the top of setup_invisible_console()
   * should be removed.
   */
/*
    if (foo())
    {
       *bUsingPipes = FALSE;
       return TRUE;
    }
*/

  /* but for now, the only way we KNOW we have a console is
   * if we created it ourselves
   */
  if (bHaveInvisConsole)
    {
      *bUsingPipes = FALSE;
      return TRUE;
    }

  /* otherwise, set up pipes */
  *bUsingPipes = TRUE;

  handle_attrs.nLength = sizeof (SECURITY_ATTRIBUTES);
  handle_attrs.bInheritHandle = TRUE;
  handle_attrs.lpSecurityDescriptor = NULL;

  /* create a pipe for child's stdin.  Don't allow child to */
  /* inherit the write end of the pipe.                     */
  CreatePipe (&hpStdInput[0], &hpStdInput[1], &handle_attrs, 0);
  SetHandleInformation (hpStdInput[1], HANDLE_FLAG_INHERIT, 0);

  /* create a pipe for child's stdout.  Don't allow child to */
  /* inherit the read end of the pipe.                       */
  CreatePipe (&hpStdOutput[0], &hpStdOutput[1], &handle_attrs, 0);
  SetHandleInformation (hpStdOutput[0], HANDLE_FLAG_INHERIT, 0);

  psi->hStdInput = hpStdInput[0];
  psi->hStdOutput = hpStdOutput[1];
  psi->hStdError = hpStdOutput[1];

  *hpToChild = hpStdInput[1];
  *hpFromChild = hpStdOutput[0];
  *hpToParent = hpStdOutput[1];
  *hpFromParent = hpStdInput[0];

  return TRUE;
}

DWORD
run2_start_child (char *cmdline, char *startin, int wait_for_child)
{
  STARTUPINFO start;
  SECURITY_ATTRIBUTES sec_attrs;
  SECURITY_DESCRIPTOR sec_desc;
  PROCESS_INFORMATION child;
  DWORD retval = 0;
  BOOL bFuncReturn;
  BOOL bHaveInvisConsole;
  BOOL bUsingPipes;
  HANDLE hToChild, hFromChild;
  HANDLE hToParent, hFromParent;

  setup_win_environ ();

#ifdef DEBUG_FORCE_PIPES
  bHaveInvisConsole = FALSE;
  FreeConsole ();
#else
  bHaveInvisConsole = run2_setup_invisible_console ();
#endif

  if (!run2_configure_startupinfo (&start, bHaveInvisConsole,
                                   &bUsingPipes,
                                   &hToChild, &hFromChild,
                                   &hToParent, &hFromParent))
    {
      fatalMsg ("Error attempting to start %s", cmdline);
      return -1;
    }

  sec_attrs.nLength = sizeof (sec_attrs);
  sec_attrs.lpSecurityDescriptor = NULL;
  sec_attrs.bInheritHandle = TRUE;

  debugMsg (2, "Using Pipes: %s", (bUsingPipes ? "yes" : "no"));

  ZeroMemory (&child, sizeof (PROCESS_INFORMATION));

  bFuncReturn = CreateProcess (NULL,       /* LPCTSTR lpApplicationName */
                               cmdline,    /* LPTSTR lpCommandLine */
                               &sec_attrs, /* LPSECURITY_ATTRIBUTES lpProcessAttributes */
                               NULL,       /* LPSECURITY_ATTRIBUTES lpThreadAttributes */
                               TRUE,       /* BOOL bInheritHandles */
                               0,          /* DWORD dwCreationFlags */
                               NULL,       /* LPVOID lpEnvironment (use parent's env) */
                               startin,    /* LPCTSTR lpCurrentDirectory */
                               &start,     /* LPSTARTUPINFO lpStartupInfo */
                               &child);    /* LPPROCESS_INFORMATION lpProcessInformation */

  if (bUsingPipes)
    {
      CloseHandle (hFromParent);
      CloseHandle (hToParent);
    }

  if (bFuncReturn)
    {
      if (wait_for_child)
        {
          if (bUsingPipes)
            {
              HANDLE handles[2] = { child.hProcess, hFromChild };
              COMMTIMEOUTS timeouts;

              /* only read bytes that are ready, and return immediately */
              GetCommTimeouts (hFromChild, &timeouts);
              timeouts.ReadIntervalTimeout = MAXDWORD;
              timeouts.ReadTotalTimeoutMultiplier = 0;
              timeouts.ReadTotalTimeoutConstant = 0;
              SetCommTimeouts (hFromChild, &timeouts);

              while (WaitForMultipleObjects (2, handles, FALSE, INFINITE) ==
                     WAIT_OBJECT_0 + 1)
                {
                  char buffer[1024];
                  DWORD dwRead;
                  ReadFile (hFromChild, buffer, 1024, &dwRead, NULL);
                }
            }
          else
            {
              WaitForSingleObject (child.hProcess, INFINITE);
            }
          GetExitCodeProcess (child.hProcess, &retval);
        }
      CloseHandle (child.hThread);
      CloseHandle (child.hProcess);
      if (bUsingPipes)
        {
          CloseHandle (hFromChild);
          CloseHandle (hToChild);
        }
    }
  else
    {
      if (bUsingPipes)
        {
          CloseHandle (hFromChild);
          CloseHandle (hToChild);
        }
      fatalMsg ("Could not start %s", cmdline);
      return -1;
    }
  return retval;
}

